<?php

class Crud2Code extends CCodeModel {

    public $generateComponents = true;
    public $generateLayouts = true;
    public $layoutPath = 'application.views.layouts'; // default
    public $layoutPrefix = '';
    public $theme = 'default';
    public $moduleID = 'admin';
    // For models
    public $tables;
    public $modelClass;
    public $modelPath = 'application.models';
    public $baseClass = 'CActiveRecord';
    public $buildRelations = true;
    public $tablePrefix = '';
    // For controllers
    public $baseControllerClass = 'Controller';

    /**
     * @var array list of candidate relation code. The array are indexed by AR class names and relation names.
     * Each element represents the code of the one relation in one AR class.
     */
    protected $relations;

    public function init() {
        if (Yii::app()->db === null)
            throw new CHttpException(500, 'An active "db" connection is required to run this generator.');
        parent::init();
    }

    public function rules() {
        return array_merge(parent::rules(), array(
                    array('layoutPrefix', 'filter', 'filter' => 'trim'),
                    array('theme', 'required'),
                    array('theme', 'in', 'range' => array_keys($this->getThemes()), 'message' => 'Selected theme doesn\'t exists.'),
                    array('generateComponents, generateLayouts, layoutPrefix, theme', 'sticky'),
                    array('moduleID', 'filter', 'filter' => 'trim'),
                    array('moduleID', 'required'),
                    array('moduleID', 'match', 'pattern' => '/^\w+$/', 'message' => '{attribute} should only contain word characters.'),
                    array('moduleID', 'moduleExistsValidator'),
                    //  array('controller', 'match', 'pattern' => '/^\w+$/', 'message' => '{attribute} should only contain word characters.'),
                    array('tables', 'safe'),
                ));
    }

    public function moduleExistsValidator($attribute, $params) {
        $_module = Yii::app()->getModule($this->$attribute);
        if ($_module == null)
            $this->addError($attribute, 'Module does not exist!');
    }

    public function attributeLabels() {
        return array(
            'generateComponents' => 'Generate Components and Widgets',
            'generateLayouts' => 'Generate Layouts',
            'layoutPrefix' => 'Layout Prefix',
            'theme' => 'Theme',
            'moduleID' => 'Module ID',
            'tables' => 'DB Tables',
        );
    }

    public function requiredTemplates() {
        return array(
            'layouts' . DIRECTORY_SEPARATOR . 'main.php',
            'controller.php',
            'model.php',
            'module.php',
        );
    }

    public function prepareModels($tableName, $modelPath, $templatePath) {
        $className = $this->generateClassName($tableName);
        $schema = '';
        $table = Yii::app()->db->schema->getTable($tableName);
        $this->relations = $this->generateRelations();
        $params = array(
            'tableName' => $schema === '' ? $tableName : $schema . '.' . $tableName,
            'modelClass' => $className,
            'columns' => $table->columns,
            'labels' => $this->generateLabels($table),
            'rules' => $this->generateRules($table),
            'relations' => isset($this->relations[$className]) ? $this->relations[$className] : array(),
        );

        return new CCodeFile(
                        Yii::getPathOfAlias($modelPath) . '/' . $className . '.php',
                        $this->render($templatePath . '/model.php', $params)
        );
    }

    protected function generateRelations() {
        if (!$this->buildRelations)
            return array();
        $relations = array();
        foreach (Yii::app()->db->schema->getTables() as $table) {
            if ($this->tablePrefix != '' && strpos($table->name, $this->tablePrefix) !== 0)
                continue;
            $tableName = $table->name;

            if ($this->isRelationTable($table)) {
                $pks = $table->primaryKey;
                $fks = $table->foreignKeys;

                $table0 = $fks[$pks[0]][0];
                $table1 = $fks[$pks[1]][0];
                $className0 = $this->generateClassName($table0);
                $className1 = $this->generateClassName($table1);

                $unprefixedTableName = $this->removePrefix($tableName);

                $relationName = $this->generateRelationName($table0, $table1, true);
                $relations[$className0][$relationName] = "array(self::MANY_MANY, '$className1', '$unprefixedTableName($pks[0], $pks[1])')";

                $relationName = $this->generateRelationName($table1, $table0, true);
                $relations[$className1][$relationName] = "array(self::MANY_MANY, '$className0', '$unprefixedTableName($pks[1], $pks[0])')";
            } else {
                $className = $this->generateClassName($tableName);
                foreach ($table->foreignKeys as $fkName => $fkEntry) {
                    // Put table and key name in variables for easier reading
                    $refTable = $fkEntry[0]; // Table name that current fk references to
                    $refKey = $fkEntry[1];   // Key in that table being referenced
                    $refClassName = $this->generateClassName($refTable);

                    // Add relation for this table
                    $relationName = $this->generateRelationName($tableName, $fkName, false);
                    $relations[$className][$relationName] = "array(self::BELONGS_TO, '$refClassName', '$fkName')";

                    // Add relation for the referenced table
                    $relationType = $table->primaryKey === $fkName ? 'HAS_ONE' : 'HAS_MANY';
                    $relationName = $this->generateRelationName($refTable, $this->removePrefix($tableName, false), $relationType === 'HAS_MANY');
                    $i = 1;
                    $rawName = $relationName;
                    while (isset($relations[$refClassName][$relationName]))
                        $relationName = $rawName . ($i++);
                    $relations[$refClassName][$relationName] = "array(self::$relationType, '$className', '$fkName')";
                }
            }
        }
        return $relations;
    }

    /**
     * Checks if the given table is a "many to many" pivot table.
     * Their PK has 2 fields, and both of those fields are also FK to other separate tables.
     * @param CDbTableSchema table to inspect
     * @return boolean true if table matches description of helpter table.
     */
    protected function isRelationTable($table) {
        $pk = $table->primaryKey;
        return (count($pk) === 2 // we want 2 columns
                && isset($table->foreignKeys[$pk[0]]) // pk column 1 is also a foreign key
                && isset($table->foreignKeys[$pk[1]]) // pk column 2 is also a foriegn key
                && $table->foreignKeys[$pk[0]][0] !== $table->foreignKeys[$pk[1]][0]); // and the foreign keys point different tables
    }

    /**
     * Generate a name for use as a relation name (inside relations() function in a model).
     * @param string the name of the table to hold the relation
     * @param string the foreign key name
     * @param boolean whether the relation would contain multiple objects
     * @return string the relation name
     */
    protected function generateRelationName($tableName, $fkName, $multiple) {
        if (strcasecmp(substr($fkName, -2), 'id') === 0 && strcasecmp($fkName, 'id'))
            $relationName = rtrim(substr($fkName, 0, -2), '_');
        else
            $relationName = $fkName;
        $relationName[0] = strtolower($relationName);

        if ($multiple)
            $relationName = $this->pluralize($relationName);

        $names = preg_split('/_+/', $relationName, -1, PREG_SPLIT_NO_EMPTY);
        if (empty($names))
            return $relationName;  // unlikely
        for ($name = $names[0], $i = 1; $i < count($names); ++$i)
            $name.=ucfirst($names[$i]);

        $rawName = $name;
        $table = Yii::app()->db->schema->getTable($tableName);
        $i = 0;
        while (isset($table->columns[$name]))
            $name = $rawName . ($i++);

        return $name;
    }

    protected function removePrefix($tableName, $addBrackets = true) {
        if ($addBrackets && Yii::app()->db->tablePrefix == '')
            return $tableName;
        $prefix = $this->tablePrefix != '' ? $this->tablePrefix : Yii::app()->db->tablePrefix;
        if ($prefix != '') {
            if ($addBrackets && Yii::app()->db->tablePrefix != '') {
                $prefix = Yii::app()->db->tablePrefix;
                $lb = '{{';
                $rb = '}}';
            }
            else
                $lb = $rb = '';
            if (($pos = strrpos($tableName, '.')) !== false) {
                $schema = substr($tableName, 0, $pos);
                $name = substr($tableName, $pos + 1);
                if (strpos($name, $prefix) === 0)
                    return $schema . '.' . $lb . substr($name, strlen($prefix)) . $rb;
            }
            else if (strpos($tableName, $prefix) === 0)
                return $lb . substr($tableName, strlen($prefix)) . $rb;
        }
        return $tableName;
    }

    protected function generateClassName($tableName) {
        $className = '';
        foreach (explode('_', $tableName) as $name) {
            if ($name !== '')
                $className.=ucfirst($name);
        }
        return $className;
    }

    public function generateLabels($table) {
        $labels = array();
        foreach ($table->columns as $column) {
            $label = ucwords(trim(strtolower(str_replace(array('-', '_'), ' ', preg_replace('/(?<![A-Z])[A-Z]/', ' \0', $column->name)))));
            $label = preg_replace('/\s+/', ' ', $label);
            if (strcasecmp(substr($label, -3), ' id') === 0)
                $label = substr($label, 0, -3);
            if ($label === 'Id')
                $label = 'ID';
            $labels[$column->name] = $label;
        }
        return $labels;
    }

    public function generateRules($table) {
        $rules = array();
        $required = array();
        $integers = array();
        $numerical = array();
        $length = array();
        $safe = array();
        foreach ($table->columns as $column) {
            if ($column->autoIncrement)
                continue;
            $r = !$column->allowNull && $column->defaultValue === null;
            if ($r)
                $required[] = $column->name;
            if ($column->type === 'integer')
                $integers[] = $column->name;
            else if ($column->type === 'double')
                $numerical[] = $column->name;
            else if ($column->type === 'string' && $column->size > 0)
                $length[$column->size][] = $column->name;
            else if (!$column->isPrimaryKey && !$r)
                $safe[] = $column->name;
        }
        if ($required !== array())
            $rules[] = "array('" . implode(', ', $required) . "', 'required')";
        if ($integers !== array())
            $rules[] = "array('" . implode(', ', $integers) . "', 'numerical', 'integerOnly'=>true)";
        if ($numerical !== array())
            $rules[] = "array('" . implode(', ', $numerical) . "', 'numerical')";
        if ($length !== array()) {
            foreach ($length as $len => $cols)
                $rules[] = "array('" . implode(', ', $cols) . "', 'length', 'max'=>$len)";
        }
        if ($safe !== array())
            $rules[] = "array('" . implode(', ', $safe) . "', 'safe')";

        return $rules;
    }

    public function prepare() {
        $this->files = array();
        $templatePath = $this->templatePath;
        $modulePath = $this->modulePath;
        $moduleTemplateFile = $templatePath . DIRECTORY_SEPARATOR . 'module.php';
        $controllerTemplateFile = $templatePath . DIRECTORY_SEPARATOR . 'controller.php';
        $restrictedTemplates = array('controller.php', 'module.php', 'model.php');
        $modelPath = 'application.modules.' . $this->moduleID . '.models';
        $controllerPathAlias = 'application.modules.' . $this->moduleID . '.controllers';
        $controllerPath = YiiBase::getPathOfAlias($controllerPathAlias);
        $viewsPathAlias = 'application.modules.' . $this->moduleID . '.views';
        $viewsPath = YiiBase::getPathOfAlias($viewsPathAlias);
        // Render module file from module template
        $this->files[] = new CCodeFile(
                        $modulePath . DIRECTORY_SEPARATOR . $this->moduleClass . '.php',
                        $this->render($moduleTemplateFile)
        );

        // Render model files from selected tables
        foreach ($this->tables as $tableName) {
            $controllerID =
                    $this->files[] = $this->prepareModels($tableName, $modelPath, $templatePath);
            // Render controller file from controller template for each model
            $params = array(
                'controllerClass' => $this->generateClassName($tableName) . 'Controller',
                'modelClass' => $this->generateClassName($tableName)
            );
            $this->files[] = new CCodeFile(
                            $controllerPath . DIRECTORY_SEPARATOR . $this->generateClassName($tableName) . 'Controller.php',
                            $this->render($controllerTemplateFile, $params)
            );

            $files = scandir($templatePath);
            foreach ($files as $file) {
                if (is_file($templatePath . '/' . $file) && CFileHelper::getExtension($file) === 'php' && !in_array($file, $restrictedTemplates)) {
                    $this->files[] = new CCodeFile(
                                    $this->getViewPath($this->generateClassName($tableName)) . DIRECTORY_SEPARATOR . $file,
                                    $this->render($templatePath . '/' . $file, array(
                                        'tableSchema' => Yii::app()->db->schema->getTable($tableName),
                                        'modelClass' => $this->generateClassName($tableName)
                                    ))
                    );
                }
            }
        }

        if ($this->generateComponents) {
            $templatePath = $this->templatePath . DIRECTORY_SEPARATOR . 'components';
            $files = scandir($templatePath);
            foreach ($files as $file) {
                if (is_file($templatePath . '/' . $file) && CFileHelper::getExtension($file) === 'php') {
                    $this->files[] = new CCodeFile(
                                    Yii::getPathOfAlias('application.components') . DIRECTORY_SEPARATOR . $file,
                                    $this->render($templatePath . '/' . $file)
                    );
                }
            }
        }

        $templatePath = $this->templatePath . DIRECTORY_SEPARATOR . 'components' . DIRECTORY_SEPARATOR . 'widgets';
        $files = scandir($templatePath);
        foreach ($files as $file) {
            if (is_file($templatePath . '/' . $file) && CFileHelper::getExtension($file) === 'php') {
                $this->files[] = new CCodeFile(
                                Yii::getPathOfAlias('application.components.widgets') . DIRECTORY_SEPARATOR . $file,
                                $this->render($templatePath . '/' . $file)
                );
            }
        }

        if ($this->generateLayouts) {
            $templatePath = $this->templatePath . DIRECTORY_SEPARATOR . 'layouts';
            $files = scandir($templatePath);
            foreach ($files as $file) {
                if (is_file($templatePath . '/' . $file) && CFileHelper::getExtension($file) === 'php') {
                    $this->files[] = new CCodeFile(
                                    $this->modulePath . DIRECTORY_SEPARATOR . 'views' . DIRECTORY_SEPARATOR . 'layouts' . DIRECTORY_SEPARATOR . $file,
                                    $this->render($templatePath . '/' . $file, array(
                                        'moduleID' => $this->moduleID,
                                    ))
                    );
                }
            }
        }

        // Specific controllers from /default/controllers directory
        $templatePath = $this->templatePath . DIRECTORY_SEPARATOR . 'controllers';
        $files = scandir($templatePath);
        foreach ($files as $file) {
            if (is_file($templatePath . '/' . $file) && CFileHelper::getExtension($file) === 'php') {
                $this->files[] = new CCodeFile(
                                $this->modulePath . DIRECTORY_SEPARATOR . 'controllers' . DIRECTORY_SEPARATOR . $file,
                                $this->render($templatePath . '/' . $file)
                );
            }
        }

        // Default index view
        $this->files[] = new CCodeFile(
                        $viewsPath . DIRECTORY_SEPARATOR . 'default' . DIRECTORY_SEPARATOR . 'index.php',
                        $this->render($this->templatePath . '/views/default/index.php')
        );

        /*
         *  Pages files from /views/pages/
         */
        $generatedModels = array();
        $modelsInfo = array(); // Information about models generated
        foreach ($this->tables as $tableName) {
            $modelsInfo[] = array(
                'name' => $this->generateClassName($tableName),
                'url' => Yii::app()->createUrl('/admin/' . strtolower($this->generateClassName($tableName)))
            );
        }
        //Information about tables
        $tableInfo = array();
        foreach(Yii::app()->db->schema->tables as $table) {
            $t = $table;
            $tableInfo[] = array(
                'tableName' => '1'
            );
        }
        
        $templatePath = $this->templatePath . DIRECTORY_SEPARATOR . 'views' . DIRECTORY_SEPARATOR . 'pages';
        $files = scandir($templatePath);
        foreach ($files as $file) {
            if (is_file($templatePath . '/' . $file) && CFileHelper::getExtension($file) === 'php') {
                $this->files[] = new CCodeFile(
                                $viewsPath . DIRECTORY_SEPARATOR . 'default' . DIRECTORY_SEPARATOR . 'pages' . DIRECTORY_SEPARATOR . $file,
                                $this->render($templatePath . DIRECTORY_SEPARATOR . $file,
                                        array(
                                            'modelsInfo' => $modelsInfo
                                        ))
                );
            }
        }


        $rel = $this->relations;
        /*
          // Render controller file from controller template
          $this->files[] = new CCodeFile(
          $this->controllerFile,
          $this->render($controllerTemplateFile)
          );
          $files = scandir($templatePath);
          foreach ($files as $file) {
          if (is_file($templatePath . '/' . $file) && CFileHelper::getExtension($file) === 'php' && $file !== 'controller.php' && $file !== 'module.php') {
          $this->files[] = new CCodeFile(
          $this->viewPath . DIRECTORY_SEPARATOR . $file,
          $this->render($templatePath . '/' . $file)
          );
          }
          }
          if ($this->generateComponents) {
          $templatePath = $this->templatePath . DIRECTORY_SEPARATOR . 'components';
          $files = scandir($templatePath);
          foreach ($files as $file) {
          if (is_file($templatePath . '/' . $file) && CFileHelper::getExtension($file) === 'php') {
          $this->files[] = new CCodeFile(
          Yii::getPathOfAlias('application.components') . DIRECTORY_SEPARATOR . $file,
          $this->render($templatePath . '/' . $file)
          );
          }
          }
          $templatePath = $this->templatePath . DIRECTORY_SEPARATOR . 'components' . DIRECTORY_SEPARATOR . 'widgets';
          $files = scandir($templatePath);
          foreach ($files as $file) {
          if (is_file($templatePath . '/' . $file) && CFileHelper::getExtension($file) === 'php') {
          $this->files[] = new CCodeFile(
          Yii::getPathOfAlias('application.components.widgets') . DIRECTORY_SEPARATOR . $file,
          $this->render($templatePath . '/' . $file)
          );
          }
          }
          }
          if ($this->generateLayouts) {
          $templatePath = $this->templatePath . DIRECTORY_SEPARATOR . 'layouts';
          $files = scandir($templatePath);
          foreach ($files as $file) {
          if (is_file($templatePath . '/' . $file) && CFileHelper::getExtension($file) === 'php') {
          $this->files[] = new CCodeFile(
          $this->layoutPath . DIRECTORY_SEPARATOR . $file,
          $this->render($templatePath . '/' . $file)
          );
          }
          }
          }

         */
    }

    public function getLayoutPath() {
        return rtrim($this->getModule()->getLayoutPath() . DIRECTORY_SEPARATOR . str_replace('.', DIRECTORY_SEPARATOR, $this->layoutPrefix), DIRECTORY_SEPARATOR);
    }

    public function getRelativeLayoutPath() {
        return rtrim('layouts/' . str_replace('.', '/', $this->layoutPrefix), '/');
    }

    public function generateInputLabel($modelClass, $column) {
        return "CHtml::activeLabelEx(\$model, '{$column->name}', array('class' => 'label'))";
    }

    public function generateActiveLabel($modelClass, $column) {
        return "\$form->labelEx(\$model, '{$column->name}', array('class' => 'label'))";
    }

    public function generateInputField($modelClass, $column) {
        if ($column->type === 'boolean') {
            return "CHtml::activeCheckBox(\$model, '{$column->name}', array('class' => 'label'))";
        } else {
            if (stripos($column->dbType, 'text') !== false) {
                return "CHtml::activeTextArea(\$model, '{$column->name}', array('rows' => 6, 'cols' => 50, 'class' => 'text_area'))";
            } else {
                if (preg_match('/^(password|pass|passwd|passcode)$/i', $column->name)) {
                    $inputField = 'activePasswordField';
                } else {
                    $inputField = 'activeTextField';
                }
                if ($column->type !== 'string' || $column->size === null) {
                    return "CHtml::{$inputField}(\$model, '{$column->name}', array('class' => 'text_field'))";
                } else {
                    if (($size = $maxLength = $column->size) > 60) {
                        $size = 60;
                    }
                    return "CHtml::{$inputField}(\$model, '{$column->name}', array('size' => {$size}, 'maxlength' => {$maxLength}, 'class' => 'text_field'))";
                }
            }
        }
    }

    public function generateActiveField($modelClass, $column) {
        if ($column->type === 'boolean') {
            return "\$form->checkBox(\$model, '{$column->name}')";
        } else {
            if (stripos($column->dbType, 'text') !== false) {
                return "\$form->textArea(\$model, '{$column->name}', array('rows' => 6, 'cols' => 50, 'class' => 'text_area'))";
            } else {
                if (preg_match('/^(password|pass|passwd|passcode)$/i', $column->name)) {
                    $inputField = 'passwordField';
                } else {
                    $inputField = 'textField';
                }
                if ($column->type !== 'string' || $column->size === null) {
                    return "\$form->{$inputField}(\$model, '{$column->name}', array('class' => 'text_field'))";
                } else {
                    if (($size = $maxLength = $column->size) > 60) {
                        $size = 60;
                    }
                    return "\$form->{$inputField}(\$model, '{$column->name}', array('size' => {$size}, 'maxlength' => {$maxLength}, 'class' => 'text_field'))";
                }
            }
        }
    }

    public function getThemes() {
        return array(
            'default' => 'Default',
            'activo' => 'Activo 2',
            'red' => 'Red',
            'amro' => 'Amro',
            'bec' => 'Bec',
            'bec-green' => 'Bec-Green',
            'blue' => 'Blue',
            'djime-cerulean' => 'Djime-Cerulean',
            'drastic-dark' => 'Drastic Dark',
            'kathleene' => 'Kathleene',
            'olive' => 'Olive',
            'orange' => 'Orange',
            'reidb-greenish' => 'Greenish',
            'warehouse' => 'Warehouse',
        );
    }

    public function getModuleClass() {
        return ucfirst($this->moduleID) . 'Module';
    }

    public function getModulePath() {
        return Yii::app()->modulePath . DIRECTORY_SEPARATOR . $this->moduleID;
    }

    public function getControllerFile() {
        $module = $this->getModule();
        $id = $this->getControllerID();
        if (($pos = strrpos($id, '/')) !== false)
            $id[$pos + 1] = strtoupper($id[$pos + 1]);
        else
            $id[0] = strtoupper($id[0]);
        return $module->getControllerPath() . '/' . $id . 'Controller.php';
    }

    public function getModule() {
        /*   if (($pos = strpos($this->moduleID . '/' . $this->controller, '/')) !== false) {
          $id = substr($this->moduleID . '/' . $this->controller, 0, $pos);
          if (($module = Yii::app()->getModule($id)) !== null)
          return $module;
          } */
        if (($module = Yii::app()->getModule($this->moduleID)) !== null)
            return $module;
        return Yii::app();
    }

    public function getControllerID() {
        if ($this->getModule() !== Yii::app())
            $id = substr($this->moduleID . '/' . $this->controller, strpos($this->moduleID . '/' . $this->controller, '/') + 1);
        else
            $id = $this->moduleID . '/' . $this->controller;
        if (($pos = strrpos($id, '/')) !== false)
            $id[$pos + 1] = strtolower($id[$pos + 1]);
        else
            $id[0] = strtolower($id[0]);
        return $id;
    }

    public function getViewPath($controllerID) {
        return $this->getModule()->getViewPath() . '/' . $controllerID;
    }

}